package com.miyake.client.datamanage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.googlecode.gchart.client.GChart;
import com.googlecode.gchart.client.GChart.AnnotationLocation;
import com.googlecode.gchart.client.GChart.Curve;
import com.googlecode.gchart.client.GChart.SymbolType;
import com.googlecode.gchart.client.GChartCanvasLite;
import com.miyake.client.tools.SmallButton;

public abstract class DynamicChart extends Composite {
	private static final int MARKER_ID = 0;
	private static final int MAIN_PLOT_ID = 1;
	private GChart chart;
	
	private List<Marker> markers = new ArrayList<Marker>();
	protected boolean bDraggin = false;
	protected int selectedMarker = 0;
//	private PlotData plotData;
	private Map<String, Integer> map = new HashMap<String, Integer>();
	protected int dragX;
	protected int dragY;
	RadioButton zoom = new RadioButton("Zoom");
	RadioButton marker = new RadioButton("Marker");
	RadioButton move = new RadioButton("Move");
	
//	abstract void onZoomOut(double x, double y);
//	abstract void onZoomIn(double x, double y);
	class ZoomProperty {
		
		public ZoomProperty(double x, double y, double spanx, double spany) {
			this.x = x;
			this.y = y;
			this.spanx = spanx;
			this.spany = spany;
		}
		double x;
		double y;
		double spanx;
		double spany;
	};

	private LinkedList<ZoomProperty> zoomList = new LinkedList<ZoomProperty>();
	
	abstract void onScaleChange(double x, double y, double spanx, double spany);
	
	public DynamicChart() {
		VerticalPanel panel = new VerticalPanel();
		initWidget(panel);
		createChart(panel);
		createTools(panel);
	}
	
	private void createTools(VerticalPanel panel) {
		HorizontalPanel zoomPanel = new HorizontalPanel();
		zoomPanel.setBorderWidth(1);
		panel.add(zoomPanel);
		
		zoomPanel.add(new SmallButton("Zoom", new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				zoomIn();
			}
		}));
		
		zoomPanel.add(new SmallButton("Zoom Vertically", new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				zoomVerticalIn();
			}
		}));		

		zoomPanel.add(new SmallButton("Zoom Horizontal", new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				zoomHorizontalIn();
			}
		}));	
		
		zoomPanel.add(new SmallButton("Previous", new ClickHandler(){
			@Override
			public void onClick(ClickEvent event) {
				zoomOut();
			} 
		}));
		
		zoomPanel.add(new SmallButton("Default", new ClickHandler(){
			@Override
			public void onClick(ClickEvent event) {
				defaultScale();
			} 
		}));
		
		zoom.setText("Zoom");
		marker.setText("Marker");
		move.setText("Move");
		HorizontalPanel clickPanel = new HorizontalPanel();
		panel.add(clickPanel);
		clickPanel.add(zoom);
		clickPanel.add(marker);
		clickPanel.add(move);
		zoom.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
			@Override
			public void onValueChange(ValueChangeEvent<Boolean> event) {
				if (event.getValue()) {
					marker.setValue(false);
					move.setValue(false);
				}
			}
		});
		marker.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
			@Override
			public void onValueChange(ValueChangeEvent<Boolean> event) {
				if (event.getValue()) {
					zoom.setValue(false);
					move.setValue(false);
				}
			}
		});	
		move.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
			@Override
			public void onValueChange(ValueChangeEvent<Boolean> event) {
				if (event.getValue()) {
					zoom.setValue(false);
					marker.setValue(false);
				}
			}
		});	
	}
	
	protected void defaultScale() {
		ZoomProperty p = zoomList.getFirst();
		if (p != null) {
			onScaleChange(p.x, p.y, p.spanx, p.spany);
		}
	}

	protected void zoomHorizontalIn() {
		double xmin = chart.getXAxis().getAxisMin();
		double xmax = chart.getXAxis().getAxisMax();
		
		double ymin = chart.getYAxis().getAxisMin();
		double ymax = chart.getYAxis().getAxisMax();
		
		double center = (xmin + xmax) / 2.0;
		double middle = (ymax + ymin) / 2.0;
		
		zoomList.add(new ZoomProperty(center, middle, (xmax - xmin) / 2.0, (ymax - ymin)));
		onScaleChange(center, middle, (xmax - xmin) / 2.0, (ymax - ymin));
	}

	protected void zoomVerticalIn() {
		double xmin = chart.getXAxis().getAxisMin();
		double xmax = chart.getXAxis().getAxisMax();
		
		double ymin = chart.getYAxis().getAxisMin();
		double ymax = chart.getYAxis().getAxisMax();
		
		double center = (xmin + xmax) / 2.0;
		double middle = (ymax + ymin) / 2.0;
		
		zoomList.add(new ZoomProperty(center, middle, (xmax - xmin), (ymax - ymin) / 2.0));
		onScaleChange(center, middle, (xmax - xmin), (ymax - ymin) / 2.0);
	}

	protected void createChart(VerticalPanel panel) {
		if (chart == null) {
			chart = new GChart();
			panel.add(chart);
			
			chart.setClipToPlotArea(true);
			chart.addMouseDownHandler(new MouseDownHandler() {
				@Override
				public void onMouseDown(MouseDownEvent event) {
					//bDraggin = true;
				   if (null == chart.getTouchedPoint()) { 
					   
				   }
				   else {
					   if (marker.getValue() == true) {
						   findSelectedMarker();
					   }
				   }
				}
				
			});
			
			chart.addMouseMoveHandler(new MouseMoveHandler() {
				@Override
				public void onMouseMove(MouseMoveEvent event) {
					event.preventDefault();
					if (bDraggin && selectedMarker >= 0) {
						markers.get(selectedMarker).setX(chart.getXAxis().getMouseCoordinate());
						drawMarker();
					}
				}
			});
			
			chart.addMouseUpHandler(new MouseUpHandler() {
				@Override
				public void onMouseUp(MouseUpEvent event) {
					bDraggin = false;
				}
			});

			chart.addClickHandler(new ClickHandler() {
				@Override
				public void onClick(ClickEvent event) {
			         double xPx = Window.getScrollLeft()
	                     + Event.getCurrentEvent().getClientX()
	                     - getAbsoluteLeft()
	                     - chart.getYAxis().getAxisLabelThickness()
	                     - chart.getYAxis().getTickLabelThickness()
	                     - chart.getYAxis().getTickLength();
			         double yPx = Window.getScrollTop()
			         	+ Event.getCurrentEvent().getClientY()
			         	- getAbsoluteTop()      
			         	- chart.getChartTitleThickness();
			         double x = chart.getXAxis().getAxisMin()*(1-xPx/chart.getXChartSize()) +
			         	chart.getXAxis().getAxisMax()*(xPx/chart.getXChartSize());
		         // note the pixelY-to-cartesianY min/max swap
			         double y = chart.getYAxis().getAxisMax()*(1-yPx/chart.getYChartSize()) +
			         	chart.getYAxis().getAxisMin()*(yPx/chart.getYChartSize());
			         
			         double spanx = chart.getXAxis().getAxisMax() - chart.getXAxis().getAxisMin();
			         double spany = chart.getYAxis().getAxisMax() - chart.getYAxis().getAxisMin();

			         if (zoom.getValue()) {
				         zoomList.add(new ZoomProperty(x, y, spanx / 2.0, spany / 2.0));
				         onScaleChange(x, y, spanx / 2.0, spany / 2.0);
			         }
			         else if (move.getValue()){
			        	 onScaleChange(x, y, spanx, spany);
			         }
				}
			});
		}
		
//		chart.setChartTitle("Chart");
		chart.setChartSize(500, 300);
		
		chart.getYAxis().setHasGridlines(true);
		chart.getXAxis().setHasGridlines(true);
//		chart.getYAxis().setAxisMax(100);
//		chart.getYAxis().setAxisMin(0);
		initMartker();

//		chart.addCurve();
		chart.getYAxis().setHasGridlines(true);
		chart.getXAxis().setHasGridlines(true);
		
//		GChartCanvasLite canvas = GChart.getCanvasFactory().create();
//		canvas.moveTo(0, 0);
//		canvas.lineTo(100, 100);
		chart.update();
	}
	protected void initMartker() {
		chart.addCurve(); // Marker
		chart.getCurve(MARKER_ID).getSymbol().setHoverSelectionHeight(1);
		chart.getCurve(MARKER_ID).getSymbol().setHoverSelectionBorderWidth(1);
		chart.getCurve(MARKER_ID).getSymbol().setHoverSelectionSymbolType(SymbolType.XGRIDLINE);
		chart.getCurve(MARKER_ID).getSymbol().setHoverLocation(AnnotationLocation.SOUTHWEST);
		chart.getCurve(MARKER_ID).getSymbol().setHovertextTemplate(
	          GChart.formatAsHovertext("x=${x},y=${y}")); 	    
		chart.getCurve(MARKER_ID).setClipToPlotArea(true);
	}
	
	protected void zoomOut() {
		//PlotSetting scale = new PlotSetting();
		double xmin = chart.getXAxis().getAxisMin();
		double xmax = chart.getXAxis().getAxisMax();
		double ymin = chart.getYAxis().getAxisMin();
		double ymax = chart.getYAxis().getAxisMax();
		
		double center = (xmin + xmax) / 2.0;
		double middle = (ymax + ymin) / 2.0;
		
		ZoomProperty p = zoomList.getLast();
		if (p != null) {
			onScaleChange(p.x, p.y, p.spanx, p.spany);
			if (zoomList.size() > 1) {
				zoomList.removeLast();
			}
		}
		//onScaleChange(center, middle, (xmax - xmin) * 2.0, (ymax - ymin) * 2.0);
	}

	protected void zoomIn() {
		//PlotSetting scale = new PlotSetting();
		double xmin = chart.getXAxis().getAxisMin();
		double xmax = chart.getXAxis().getAxisMax();
		
		double ymin = chart.getYAxis().getAxisMin();
		double ymax = chart.getYAxis().getAxisMax();
		
		double center = (xmin + xmax) / 2.0;
		double middle = (ymax + ymin) / 2.0;
		
		zoomList.add(new ZoomProperty(center, middle, (xmax - xmin) / 2.0, (ymax - ymin) / 2.0));
		onScaleChange(center, middle, (xmax - xmin) / 2.0, (ymax - ymin) / 2.0);
	}
	
	public void addMarker(double x) {
		Marker marker = new Marker("", x) {
			@Override
			protected double getXmin() {
				return chart.getXAxis().getAxisMin();
			}

			@Override
			protected double getWidth() {
				return chart.getCurve(MAIN_PLOT_ID).getNPoints();
			}

			@Override
			protected double getXmax() {
				return chart.getXAxis().getAxisMax();
			}
			
		};
		markers.add(marker);
		drawMarker();
	}
	
	private void drawMarker() {
		chart.getCurve(MARKER_ID).clearPoints();

		for (Marker marker : markers) {
			if (marker.getPos() < 0 || marker.getPos() >= chart.getCurve(MAIN_PLOT_ID).getNPoints())
				continue;
			
			double y = chart.getCurve(MAIN_PLOT_ID).getPoint(marker.getPos()).getY();
			marker.setY(y);
			chart.getCurve(MARKER_ID).addPoint(chart.getCurve(MAIN_PLOT_ID).getPoint(marker.getPos()).getX(), y);
			chart.getCurve(MARKER_ID).getPoint().setAnnotationText(marker.getName());

		}
		chart.update();
	}

	private void findSelectedMarker() {
		selectedMarker = -1;
		if (chart.getTouchedCurve() == chart.getCurve(MARKER_ID) ){
			int i = 0;
			for (Marker marker : markers) {
			   if (marker.getName().equals(chart.getTouchedPoint().getAnnotationText())) {
				   selectedMarker = i;
				   System.out.println("Marker Selected "+marker.getName());
			   }
			   i++;
			}
		}
	}

	protected void addPlot(PlotData result, String name) {
		chart.addCurve();
		chart.getXAxis().setAxisMin(result.getXmin());
		chart.getXAxis().setAxisMax(result.getXmax());
		chart.getYAxis().setAxisMin(result.getYmin());
		chart.getYAxis().setAxisMax(result.getYmax());
		
		if (zoomList.size() == 0) {
			zoomList.add( new ZoomProperty((result.getXmin() + result.getXmax()) / 2.0, 
					(result.getYmin() + result.getYmax()) / 2.0,
					result.getXmax() - result.getXmin(),
					result.getYmax() - result.getYmin()));
		}
		map.put(name, chart.getNCurves()-1);
		Curve curve = chart.getCurve(chart.getNCurves()-1);
		if (result.getPlotType().equals(PlotData.PLOT_LINE)) {
			curve.getSymbol().setSymbolType(SymbolType.LINE);
			curve.getSymbol().setHeight(0);
			curve.getSymbol().setWidth(0);
		}
		else if(result.getPlotType().equals(PlotData.PLOT_DOT)) {
			curve.getSymbol().setSymbolType(SymbolType.NONE);
		}
		Double[] y = result.getY();
		double xstep = (result.getXmax() - result.getXmin()) / y.length;
		for (int i = 0; i < y.length; i++) {
			curve.addPoint(((double)i)*xstep + result.getXmin(), y[i].doubleValue());
		}
		this.drawMarker();
//		chart.update();
	}
	
	public void update() {
		chart.update();
	}
	public void remove(String name) {
		chart.removeCurve(map.get(name));
		map.remove(name);
	}
	public void clear() {
		chart.clearCurves();
		initMartker();
	}
	public void addMarker() {
		double pos = (chart.getXAxis().getAxisMax() + chart.getXAxis().getAxisMin())/2;
		this.addMarker(pos);
	}
	public double getSpan() {
		return chart.getXAxis().getAxisMax() - chart.getXAxis().getAxisMin();
	}
	public double getHeight() {
		return chart.getYAxis().getAxisMax() - chart.getYAxis().getAxisMin();
	}
}
abstract class Marker {
	private int pos;
	private double x;
	private double y;
	private String name;
	public Marker(String string, double x2) {
		this.name = string;
		this.setX(x2);
	}
	abstract protected double getXmin();
	abstract protected double getWidth();
	abstract protected double getXmax();
	
	public double getX() {
		return x;
	}
	public void setX(double x) {
		this.x = x;
		pos = (int)( (x - getXmin())* getWidth() / (getXmax() - getXmin()) );
	}
	
	public double getY() {
		return y;
	}
	public void setY(double y) {
		this.y = y;
	}
	public int getPos() {
		return pos;
	}
	public void setPos(int pos) {
		this.pos = pos;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
};